package com.googlecode.bluetools.common.utils;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.Map.Entry;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarInputStream;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
import java.util.zip.ZipEntry;

/**
 * DOCME
 */
public class JarUtils {

	/**
	 * Dump the contents of a JarArchive to the dpecified destination.
	 */
	public static void extract(File jarFile, File dest) throws IOException {
		if (!dest.exists()) {
			dest.mkdirs();
		}
		if (!dest.isDirectory()) {
			throw new IOException("Destination must be a directory.");
		}
		JarInputStream jin = new JarInputStream(new FileInputStream(jarFile));
		byte[] buffer = new byte[1024];

		ZipEntry entry = jin.getNextEntry();
		while (entry != null) {
			String fileName = entry.getName();
			if (fileName.charAt(fileName.length() - 1) == '/') {
				fileName = fileName.substring(0, fileName.length() - 1);
			}
			if (fileName.charAt(0) == '/') {
				fileName = fileName.substring(1);
			}
			if (File.separatorChar != '/') {
				fileName = fileName.replace('/', File.separatorChar);
			}
			File file = new File(dest, fileName);
			if (entry.isDirectory()) {
				// make sure the directory exists
				file.mkdirs();
				jin.closeEntry();
			}
			else {
				// make sure the directory exists
				File parent = file.getParentFile();
				if (parent != null && !parent.exists()) {
					parent.mkdirs();
				}

				// dump the file
				OutputStream out = new FileOutputStream(file);
				int len = 0;
				while ((len = jin.read(buffer, 0, buffer.length)) != -1) {
					out.write(buffer, 0, len);
				}
				out.flush();
				out.close();
				jin.closeEntry();
				file.setLastModified(entry.getTime());
			}
			entry = jin.getNextEntry();
		}
		/*
		 * Explicity write out the META-INF/MANIFEST.MF so that any headers such as the Class-Path are see for the
		 * unpackaged jar
		 */
		Manifest mf = jin.getManifest();
		if (mf != null) {
			File file = new File(dest, "META-INF/MANIFEST.MF");
			File parent = file.getParentFile();
			if (parent.exists() == false) {
				parent.mkdirs();
			}
			OutputStream out = new FileOutputStream(file);
			mf.write(out);
			out.flush();
			out.close();
		}
		jin.close();
	}

	/**
	 * use URLClassLoader.findResource, this method will not find resource use parent
	 * 
	 * @param loader
	 * @param descriptorName
	 * @return null if no such descriptor
	 */
	public static URL getDescriptor(URLClassLoader loader, String descriptorName) {
		return loader.findResource(descriptorName);
	}

	public static Manifest getManifest(File jarFile) throws IOException {
		return new JarFile(jarFile).getManifest();
	}

	public static String[] getClassPathFromManifest(File jarFile) throws IOException {
		Manifest mani = getManifest(jarFile);
		Attributes attrs = mani.getMainAttributes();
		String cpString = attrs.getValue("Class-Path");
		if (cpString == null || cpString.trim().equals("")) {
			return new String[0];
		}
		else {
			StringTokenizer st = new StringTokenizer(cpString);
			List cpList = new ArrayList();
			while (st.hasMoreTokens()) {
				cpList.add(st.nextToken());
			}
			return (String[]) cpList.toArray(new String[cpList.size()]);
		}
	}

	/**
	 * append external files to source jar archive and save it to dest one. NOTE: if entry name for external file has
	 * existed in source jar archive, <b>the old one will be overwritten</b>.
	 * 
	 * @param srcFile The source jar archive
	 * @param destFile The destination jar archive
	 * @param appendage The external file set, (java.io.File, String)
	 * @throws IOException
	 */
	public static void append(File srcFile, File destFile, Map/* <File, String */appendage) throws IOException {
		File tmpFoder = new File(FileUtil.getTempDir(), "tmpFolder" + System.currentTimeMillis());
		extract(srcFile, tmpFoder);
		JarOutputStream jos = null;
		try {
			for (Iterator it = appendage.entrySet().iterator(); it.hasNext();) {
				Entry entry = (Entry) it.next();
				String entryName = (String) entry.getValue();
				org.apache.commons.io.FileUtils.copyFile((File) entry.getKey(), new File(tmpFoder, entryName));
			}

			jos = new JarOutputStream(new FileOutputStream(destFile));
			jarFile(jos, "", tmpFoder);
		}
		finally {
			if (jos != null)
				jos.close();
			org.apache.commons.io.FileUtils.deleteDirectory(tmpFoder);
		}
	}

	/**
	 * append external files to source jar archive. NOTE: if entry name for external file has existed in source jar
	 * archive, <b>the old one will be overwritten</b>.
	 * 
	 * @param srcFile The source jar archive
	 * @param appendage The external file set, (java.io.File, String)
	 * @throws IOException
	 */
	public static void append(File srcFile, Map/* <File, String */appendage) throws IOException {
		append(srcFile, srcFile, appendage);
	}

	private static void jarFile(JarOutputStream writer, String parent, File file) throws IOException {
		if (file.isDirectory()) {
			if (parent.equals(""))
				parent = "/";
			else
				parent = parent + "/" + file.getName();
			File[] files = file.listFiles();
			for (int i = 0; i < files.length; i++) {
				jarFile(writer, parent, files[i]);
			}
		}
		else if (file.isFile()) {
			FileInputStream in = new FileInputStream(file);
			String entryName = removeLeadSlash(parent + "/" + file.getName());
			writer.putNextEntry(new JarEntry(entryName));
			byte[] buf = new byte[1024];
			int len;
			while ((len = in.read(buf)) > 0) {
				writer.write(buf, 0, len);
			}
			in.close();
		}
	}

	private static String removeLeadSlash(String name) {
		int idx = 0;
		char[] chName = name.toCharArray();
		while (chName[idx] == '/') {
			idx++;
		}
		return new String(chName, idx, name.length() - idx);
	}
}
